Zum Hauptinhalt springen

Erzeugungsmuster

In objektorientierten Programmiersprachen ist der Einsatz des Operators new der Standardweg zur Erzeugung von Objekten, stößt jedoch bei komplexen Anforderungen oft an Grenzen der Flexibilität. Erzeugungsmuster lösen dieses Problem, indem sie den Instanziierungsprozess in separate Methoden oder Klassen auslagern. Durch diese Kapselung wird die direkte Abhängigkeit zwischen dem Anwendungscode und der konkreten Objekterzeugung verringert, was Anpassungen erleichtert und die Softwarearchitektur robuster macht.

Der zentrale Vorteil dieses Ansatzes liegt in der gewonnenen Kontrolle und Flexibilität. Entwickler können den Erzeugungsprozess präzise steuern, etwa um Abhängigkeiten zwischen Objekten zu managen oder die Anzahl der Instanzen zu begrenzen, wie es beim Singleton-Pattern der Fall ist. Zudem muss der Aufrufer oft nur eine abstrakte Schnittstelle kennen und keine konkreten Klassen, wodurch Implementierungen ausgetauscht werden können, ohne den nutzenden Code zu ändern. Auch die Lesbarkeit verbessert sich, da komplexe Konstruktoren mit vielen Parametern durch klar definierte Erzeugungsmethoden ersetzt werden.

Demgegenüber steht der Nachteil der begrenzten Erweiterbarkeit in bestimmten Szenarien. Werden Erzeugungsmuster genutzt, um ganze Familien zusammengehöriger Objekte zu erstellen, führt das Hinzufügen neuer Objektarten oft zu Problemen. In solchen Fällen ist es meist unvermeidlich, den bestehenden Code der Erzeugungsmethoden anzupassen, was den Wartungsaufwand erhöht.

(1) Das Singleton Pattern

Das Singleton Pattern ist eines der bekanntesten Entwurfsmuster und hat das primäre Ziel, die Existenz einer Klasse auf exakt eine einzige Instanz zur Laufzeit zu beschränken. Während in der klassischen imperativen Programmierung ähnliche Ziele oft durch globale Variablen erreicht wurden, bietet das Singleton im objektorientierten Kontext eine sicherere Alternative, die unkontrollierte Seiteneffekte vermeidet. Es eignet sich besonders für zentrale Steuerungsaufgaben innerhalb einer Anwendung, wie etwa die Speicherverwaltung oder die Koordination externer Ressourcen, die über eine einzige Schnittstelle angesprochen werden müssen.

Die technische Umsetzung basiert auf der Abschottung des Konstruktors durch das Zugriffsattribut private, wodurch eine direkte Instanziierung von außen verhindert wird. Stattdessen übernimmt eine statische Methode (oft getInstance()) die volle Kontrolle über den Erzeugungsprozess. Diese Methode prüft, ob bereits eine Instanz in einer statischen Variable vorliegt, erzeugt diese gegebenenfalls beim ersten Aufruf und gibt stets die Referenz auf dieses eine Objekt zurück. Ein kritischer Aspekt bei der Implementierung ist die Gewährleistung der Threadsicherheit in Multithreaded-Umgebungen, um Race Conditions bei der Erstellung zu vermeiden.

Nachteile

Trotz seiner Popularität unterliegt das Muster deutlicher Kritik und bringt diverse Nachteile mit sich. Dazu gehören die Intransparenz für den Aufrufer, die Förderung eines eher prozeduralen statt objektorientierten Stils sowie Probleme bei der Speicherfreigabe in Sprachen ohne Garbage Collection.

Als gravierendster Nachteil gilt der Verlust der Vererbungsmöglichkeit, da die statische Erzeugungsstruktur keine sinnvolle Ableitung von Unterklassen zulässt. Zudem wird der Einsatz in modernen Multiprozessor-Architekturen zunehmend hinterfragt.

Dennoch bleiben bestimmte Einsatzszenarien valide, in denen eine strikte Zentralisierung notwendig oder effizient ist. Dazu zählen der exklusive Zugriff auf Hardwareressourcen wie Drucker, die Generierung eindeutiger IDs bei der Serialisierung oder die Performance-Optimierung bei Objekten mit sehr aufwändigen Initialisierungsprozessen, wie etwa dem Parsen großer Konfigurationsdateien.

Beispiel

Wir möchten den exklusiven Zugriff auf einen Drucker gewähren. Wir möchten also, dass nur eine einzige Instanz des Druckers zur Laufzeit existieren darf:

public class Drucker {

*** 1. Die einzige Instanz wird hier statisch gespeichert ***

private static Drucker einzigeInstanz;

*** 2. Der Konstruktor ist PRIVAT (new Drucker() wird dadurch verhindert) ***

private Drucker() {
System.out.println("Ein neuer Drucker wurde initialisiert.");
}

*** Eine öffentliche, statische Methode gibt die Instanz zurück ***
public static Drucker getInstanz() {
if (einzigeInstanz == null) {
einzigeInstanz = new Drucker();
}
return einzigeInstanz;
}

public void drucken(String text) {
System.out.println("Drucke: " + text);
}
}

1.1. Weitere Ausbauformen des Singleton Pattern

Das Singleton Pattern lässt sich durch das Multiton und das Object-Pool Pattern verallgemeinern, wobei beide Ansätze die Erzeugung mehrerer Instanzen zulassen, diese jedoch durch eine definierte Obergrenze limitieren. Technisch gesehen fungiert das Singleton dabei als ein Object-Pool mit einer Kapazität von eins. Ein entscheidender Unterschied zwischen den beiden Erweiterungen liegt in der Zugriffskontrolle: Während beim Multiton Pattern eine Instanz auch dann erneut herausgegeben werden kann, wenn sie bereits von einem anderen Anfrager genutzt wird, unterbindet der Object-Pool die gleichzeitige Verwendung eines Objekts durch mehrere Parteien strikt.

Sobald beim Object-Pool die maximale Anzahl an Instanzen ausgeschöpft ist, reagiert das System entweder mit der Rückgabe einer Null-Referenz oder blockiert den anfragenden Thread so lange, bis ein Objekt wieder verfügbar wird. Dies impliziert, dass verwendete Instanzen nach ihrem Gebrauch explizit an den Pool zurückgegeben werden müssen, was eine hohe Disziplin bei der Implementierung erfordert. Der Hauptnutzen dieses Musters liegt in der Ressourcenschonung bei teuren Erzeugungs- und Löschvorgängen von Objekten, weshalb es in der Praxis häufig für ThreadPools in Serveranwendungen eingesetzt wird.